package tracking_algorithms;

import ij.ImagePlus;
import ij.gui.NewImage;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;

import java.awt.Color;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.media.Buffer;
import javax.media.format.VideoFormat;
import javax.media.util.ImageToBuffer;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import tracking_algorithms.blob.Blob;
import tracking_algorithms.blob.BlobManager;
import tracking_algorithms.blob.BlobPublishingCamera;
import tracking_algorithms.blob.detector.FourNeighborBlobDetector;
import tracking_algorithms.camera.AbstractProcessingCamera;
import tracking_algorithms.videoprocessing.BackgroundUpdater;
import tracking_algorithms.videoprocessing.BufferAccessor;
import tracking_algorithms.videoprocessing.PixelizationDialate8Bit;
import tracking_algorithms.videoprocessing.RgbDiffEffect;
import tracking_algorithms.videoprocessing.RgbThresholdEffect;
import tracking_algorithms.videoprocessing.RgbVideoEffect;
import algos.Isovist;
import camera.AbstractCamera;
import camera.IPCam;

import com.jme3.math.Vector3f;

/**
 * This is the class responsible for setting cameras, generate the background
 * image and using the tracking algorithm to extract moving blobs.
 * 
 * @author Moamen Mokhtar
 */
public class Tracker {

	private BlobManager manager;
	private AbstractProcessingCamera blobPublishingCamera;
	public RgbVideoEffect[] processingChain;
	private ImageProcessor[] backgrounds;
	private ImageProcessor[] coloredBackgrounds;
	public BufferedImage backGround;
	public BufferedImage coloredBackGround;
	private Isovist iso[];
	public TrackerUpdate tUpdator;
	public ArrayList<Blob> blobs;
	public Blob testingBlob;
	public boolean testingPositions = false;
	public int num = 0;
	private final double PASS_RATIO_THRESHOLD = 20.0;

	public void setIsovist(Isovist[] iso) {
		this.iso = iso;
	}

	public Isovist[] getIsovist() {
		return iso;
	}

	/* Constructor */
	public Tracker(int numberOfBackgroundImages,
			ArrayList<AbstractCamera> cameras, JSlider bSizeslider,
			JSlider thresholdSlider) {
		backgrounds = new ImageProcessor[numberOfBackgroundImages];
		coloredBackgrounds = new ImageProcessor[numberOfBackgroundImages];

		tUpdator = new TrackerUpdate(cameras);

		blobPublishingCamera = new BlobPublishingCamera();
		blobPublishingCamera.createProcessingChain();
		processingChain = blobPublishingCamera.getProcessingChain();
		((FourNeighborBlobDetector) processingChain[5]).setSlider(bSizeslider);
		((RgbThresholdEffect) processingChain[2])
				.setThresholdSlider(thresholdSlider);
		testingBlob = new Blob(-1, new Rectangle(10, 10, 10, 10));
	}

	/**
	 * process input frame by using tracking algorithm processing chain
	 * 
	 * @param input
	 *            the captured image.
	 * @param videoFormat
	 * 
	 * 
	 */
	public void processFrame(Image input, Image coloredInput,
			VideoFormat videoFormat) {

		if (!testingPositions) {
			Buffer buffer = ImageToBuffer.createBuffer(input, 30);
			Buffer coloredBuffer = ImageToBuffer.createBuffer(coloredInput, 30);
			int[] iin = (int[]) buffer.getData();
			int[] iin2 = (int[]) coloredBuffer.getData();
			int w = videoFormat.getSize().width;
			int h = videoFormat.getSize().height;
			int k = 0;
			int j = 0;
			for (int i = 0; i < buffer.getLength(); i++) {
				int v = iin[i];
				int v2 = iin2[i];
				RgbVideoEffect.inputImageArray[j][k] = v;
				RgbVideoEffect.coloredInputImageArray[j][k] = v2;
				k++;
				if (k % w == 0) {
					k = 0;
					j = (j + 1) % h;

				}
			}
			boolean skipThisFrame = false;
			/**
			 * apply a processing chain to the input frame by the end of the
			 * next loop the blobs are detected and correlated
			 */
			for (RgbVideoEffect rgbv : processingChain) {
				rgbv.process(buffer, (Buffer) buffer.clone());
				if (rgbv.getName().equals(
						"RGB-Difference from Background Image")) {
					buffer.setData(((RgbDiffEffect) rgbv).getDiffer());
				}
				if (rgbv.getName().equals("RGB Threshold")) {
					buffer.setData(((RgbThresholdEffect) rgbv).getOut());
					// check if the ratio of moving pixels exceeds a threshold
					// skip
					// cameras update for this frame
					if (RgbVideoEffect.passRatio > PASS_RATIO_THRESHOLD) {
						System.out.println("Ratio = "
								+ RgbVideoEffect.passRatio);
						skipThisFrame = true;
					}
				}

				if (rgbv.getName().equals("Pixelization Dialate (8-bit)")) {
					buffer.setData(((PixelizationDialate8Bit) rgbv).getOut());
				}
				if (rgbv.getName().equals("Four-Neighbor Blob Detector")) {
					manager = ((FourNeighborBlobDetector) rgbv).getManager();
					manager.updateBlobs(manager.getUncorrelatedBlobList());
				}
			}
			blobs = new ArrayList<Blob>();
			ArrayList<Vector3f> blobsPos = new ArrayList<Vector3f>();

			for (Blob blob : getBlobs()) {

				blobs.add(blob);
				int x = (int) blob.predectedX;
				// the y co-ordinate is computed relative to the origin at the
				// bottom left point, but the image is treated as the origin is
				// at
				// the top left corner
				// so y is mirrord
				int y = (videoFormat.getSize().height - (int) blob.predectedY);
				Vector3f pos = new Vector3f(x, 0, y);
				blobsPos.add(pos);
				
			}
			if (!skipThisFrame) {
				tUpdator.update(blobsPos, iso);
			}
		} else {
			blobs = new ArrayList<Blob>();
			blobs.add(testingBlob);
			ArrayList<Vector3f> testPos = new ArrayList<Vector3f>();
			testPos.add(new Vector3f(testingBlob.bounds.x, 0,
					videoFormat.getSize().height-testingBlob.bounds.y));
			tUpdator.update(testPos, iso);
		}
		// if the frame is to be skipped due to large amount of pixels change,
		// don't update the cameras

		/** write the background image to files for test purposes */
		// processingChain[0].inputImage.findEdges();
		// try {
		// // ImageIO.write(backGround, "png", new
		// File("background_"+num+".png"));
		// ImageIO.write(RgbVideoEffect.coloredInputImage.getBufferedImage(),
		// "png", new File("coloredinput_"+num+".png"));
		// // ImageIO.write(processingChain[0].inputImage.getBufferedImage(),
		// "png", new File("input_"+num+".png"));
		// // BufferedImage buim = new
		// BufferedImage(RgbVideoEffect.backGroundArray[0].length,
		// RgbVideoEffect.backGroundArray.length, BufferedImage.TYPE_BYTE_GRAY);
		// // for (int i = 0; i < RgbVideoEffect.backGroundArray.length; i++) {
		// // for (int l = 0; l < RgbVideoEffect.backGroundArray[0].length; l++)
		// {
		// // buim.setRGB(l, i, RgbVideoEffect.backGroundArray[i][l]);
		// // }
		// // }
		// //
		// // ImageIO.write(buim, "png", new File("back_"+num+".png"));
		// } catch (IOException ex) {
		// // Logger.getLogger(Tracker.class.getName()).log(Level.SEVERE, null,
		// ex);
		// }
		num++;
	}

	private int find(int value, ArrayList<Point> values) {

		for (int i = 0; i < values.size(); i++) {
			if (values.get(i).x == value) {
				return i;
			}
		}
		return -1;
	}

	private class ListComparator implements Comparator<Point> {

		@Override
		public int compare(Point o1, Point o2) {
			// TODO Auto-generated method stub
			return ((Integer) o2.y).compareTo(o1.y);
		}
	}

	/**
	 * This function generates background image from sequence of images
	 * containing moving objects. For each pixel in every image the vale of this
	 * pixel has a counter that is incremented every time the pixel have the
	 * same value At the end these values are sorted the the value that occurred
	 * the most is likely to belong to the background image.
	 */
	public void createBackGround() {
		int value;
		int cValue;
		ArrayList<Point> values;
		ArrayList<Point> cValues;
		for (int j = 0; j < backgrounds[0].getHeight(); j++) {
			for (int i = 0; i < backgrounds[0].getWidth(); i++) {
				values = new ArrayList<Point>();
				cValues = new ArrayList<Point>();
				for (int k = 0; k < backgrounds.length; k++) {
					value = backgrounds[k].get(i, j);
					cValue = coloredBackgrounds[k].get(i, j);
					int index = find(value, values);
					if (index == -1) {
						values.add(new Point(value, 1));
						cValues.add(new Point(cValue, 1));
					} else {
						values.get(index).y = values.get(index).y + 1;
						cValues.get(index).y = cValues.get(index).y + 1;
					}

				}
				Collections.sort(values, new ListComparator());
				Collections.sort(cValues, new ListComparator());
				backgrounds[0].set(i, j, values.get(0).x);
				coloredBackgrounds[0].set(i, j, cValues.get(0).x);
			}
		}
		backgrounds[0].convertToByte(false);
		backGround = backgrounds[0].getBufferedImage();
		coloredBackGround = coloredBackgrounds[0].getBufferedImage();

		Buffer bckgnd = ImageToBuffer.createBuffer(backGround, 30);
		Buffer clrdbckgnd = ImageToBuffer.createBuffer(coloredBackGround, 30);
		ImageProcessor bckpros = new ImagePlus("", backGround).getProcessor();
		// bckpros.medianFilter();
		int w = backGround.getWidth();
		int h = backGround.getHeight();
		int k = 0;
		int j = 0;
		RgbVideoEffect.backGroundArray = new int[h][w];
		RgbVideoEffect.coloredBackGroundArray = new int[h][w];
		int[] iin = (int[]) bckgnd.getData();
		int[] iin2 = (int[]) clrdbckgnd.getData();
		for (int i = 0; i < bckgnd.getLength(); i++) {
			int v = iin[i];
			int cv = iin2[i];
			RgbVideoEffect.backGroundArray[j][k] = v;
			// RgbVideoEffect.backGroundHistory[RgbVideoEffect.historyIndex][j][k]
			// = v;
			RgbVideoEffect.coloredBackGroundArray[j][k] = cv;
			k++;
			if (k % w == 0) {
				k = 0;
				j = (j + 1) % h;
			}
		}
		// processingChain[0].backGroundArray = new byte[bckgnd.getLength()];

		RgbVideoEffect.inputImageArray = new int[h][w];
		RgbVideoEffect.coloredInputImageArray = new int[h][w];
		RgbVideoEffect.backGroundImage = bckpros;
		RgbVideoEffect.coloredBackGroundImage = new ImagePlus("",
				coloredBackGround).getProcessor();

		// for (int i = 0; i < bckgnd.getLength(); i++) {
		// processingChain[0].backGroundArray[i] =
		// ((int[])bckgnd.getData())[i]>>
		// }
		// int [] iin = (int[])bckgnd.getData();
		// for (int i = 0; i < bckgnd.getLength(); i++) {
		// int v = iin[i];
		// processingChain[0].backGroundArray[i] = (byte) ((v >> 16) & 0xff);
		// }
		// processingChain[0].coloredBackGroundArray =
		// (int[])clrdbckgnd.getData();

		// try {
		// // ImageIO.write(backGround, "png", new File("background.png"));
		// // ImageIO.write(coloredBackGround, "png", new
		// File("coloredbackground.png"));
		// } catch (IOException ex) {
		// // Logger.getLogger(Tracker.class.getName()).log(Level.SEVERE, null,
		// ex);
		// }
		backgrounds = null;
		System.gc();
	}

	/**
	 * return detected blobs
	 * 
	 * @return
	 */
	public ArrayList<Blob> getBlobs() {
		return (ArrayList<Blob>) manager.getBlobList();
	}

	/**
	 * add image to sequence of image that the background will be extracted
	 * from.
	 * 
	 * @param image
	 * @param index
	 */
	public void addImage(BufferedImage image, BufferedImage coloredInput,
			int index) {
		backgrounds[index] = new ImagePlus("", image).getProcessor();
		coloredBackgrounds[index] = new ImagePlus("", coloredInput)
				.getProcessor();
	}
}
